using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Runtime.Serialization;
using INdT.Sparta.Camera;
using INdT.Sparta.Graphics;
using INdT.Sparta.UI;

namespace INdT.Sparta
{
    [DataContract]
    [KnownType(typeof(SpartaObject))]
    [KnownType(typeof(SpartaCamera2D))]
    [KnownType(typeof(SpartaFlickable))]
    [KnownType(typeof(SpartaDrawable))]
    [KnownType(typeof(SpartaTexture))]
    [KnownType(typeof(SpartaSprite))]
    [KnownType(typeof(SpartaTiled))]
    [KnownType(typeof(SpartaButton))]
    [KnownType(typeof(SpartaText))]
    [KnownType(typeof(SpartaTextButton))]
    public class SpartaList<T> : IDisposable
    {
        private T[] objectsArray;
        private List<T> objectsList;

        [DataMember]
        public T[] Array 
        { 
            get { return objectsArray; }
            set
            {
                if (value != null)
                {
                    objectsList = value.Where(element => element != null).ToList();
                }
                else if (objectsList == null)
                {
                    objectsList = new List<T>();
                }
                else
                {
                    objectsList.Clear();
                }
                UpdateArray();
            }
        }

        public int Count { get { return objectsList.Count; } }

        public bool IsDisposed { get; protected set; }

        public SpartaList()
        {
            objectsList = new List<T>();
        }

        public void Add(T item)
        {
            objectsList.Add(item);
            UpdateArray();
        }

        public void AddBefore(T item, T before)
        {
            int count = objectsList.Count;
            int index = objectsList.IndexOf(before) - 1;

            if (index < 0)
            {
                objectsList.Insert(0, item);
            }
            else if (index >= count - 1)
            {
                objectsList.Add(item);
            }
            else
            {
                objectsList.Insert(index, item);
            }
            
            UpdateArray();
        }

        public bool Remove(T item)
        {
            bool removed = objectsList.Remove(item);
            if (removed)
            {
                UpdateArray();
                return true;
            }
            return false;
        }

        public bool Contains(T item)
        {
            return objectsList.Contains(item);
        }

        public void Clear()
        {
            objectsList.Clear();
            UpdateArray();
        }

        public T this[int i]
        {
            get { return this.objectsArray[i]; }
            set { this.objectsArray[i] = value; }
        }

        private void UpdateArray()
        {
            int i;
            int objectsCount = objectsList.Count;                // Store the current object count for performance

            if (objectsArray == null)
            {
                // No, so allocate it.
                // Allocate 20% more objects than we currently have, or 20 objects, whichever is more
                objectsArray = new T[(int)MathHelper.Max(20, objectsCount * 1.2f)];
            }
            else if (objectsCount > objectsArray.Length)
            {
                // The number of game objects has exceeded the array size.
                // Reallocate the array, adding 20% free space for further expansion.
                objectsArray = new T[(int)(objectsCount * 1.2f)];
            }

            int objectsArrayLength = objectsArray.Length;

            // Transfer the object references into the array
            for (i = 0; i < objectsArrayLength; i++)
            {
                // Is there an active object at this position?
                if (i < objectsCount)
                {
                    // Yes, so copy it to the array
                    objectsArray[i] = objectsList[i];
                }
                else
                {
                    // No, so clear any reference stored at this index position
                    objectsArray[i] = default(T);
                }
            }
        }

        public virtual void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }

            GC.SuppressFinalize(objectsList);
            if (objectsArray != null)
            {
                GC.SuppressFinalize(objectsArray);
            }
            objectsList = null;
            objectsArray = null;
            IsDisposed = true;
            GC.SuppressFinalize(this);
        }
    }
}
